package drds.plus.executor.cursor.cursor.impl;

import drds.plus.executor.ExecuteContext;
import drds.plus.executor.cursor.cursor.IAggregateCursor;
import drds.plus.executor.cursor.cursor.ISortingCursor;
import drds.plus.executor.cursor.cursor_metadata.CursorMetaData;
import drds.plus.executor.cursor.cursor_metadata.CursorMetaDataImpl;
import drds.plus.executor.function.aggregate_function.AggregateFunction;
import drds.plus.executor.function.scalar.ScalarFunction;
import drds.plus.executor.row_values.ArrayRowValues;
import drds.plus.executor.row_values.RowValues;
import drds.plus.executor.utils.Utils;
import drds.plus.sql_process.abstract_syntax_tree.configuration.ColumnMetaData;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.Item;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Function;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.FunctionType;
import drds.plus.sql_process.abstract_syntax_tree.expression.order_by.OrderBy;
import drds.plus.sql_process.type.Type;
import drds.plus.util.GeneralUtil;
import drds.tools.$;
import lombok.Getter;
import lombok.Setter;

import java.util.*;
import java.util.Map.Entry;

/**
 * 用来计算聚合函数，group by
 */
public class AggregateCursor extends SortingCursor implements IAggregateCursor {

    /**
     * 查询中涉及的所有聚合函数
     */
    @Setter
    @Getter
    protected List<Function> aggregateFunctionList = new LinkedList<Function>();
    /**
     * 查询中涉及的所有scalar函数
     */
    @Setter
    @Getter
    List<Function> scalarFunctionList = new LinkedList<Function>();
    /**
     * 当前节点是不是归并节点
     */
    @Setter
    @Getter
    boolean isMerge = false;
    @Setter
    @Getter
    List<ColumnMetaData> groupByColumnMetaDataList = new ArrayList<ColumnMetaData>();
    @Setter
    @Getter
    Map<ColumnMetaData, Object> groupByColumnMetaDataToValueMap = null;
    @Setter
    @Getter
    boolean end = false;
    @Setter
    @Getter
    RowValues rowData = null;
    @Setter
    @Getter
    boolean isFirst = true;
    @Setter
    @Getter
    private boolean cursorMetaDataInited = false;
    @Setter
    @Getter
    private CursorMetaData cursorMetaData = null;
    @Setter
    @Getter
    private ExecuteContext executeContext;

    public AggregateCursor(ExecuteContext executeContext, ISortingCursor sortingCursor, List<Function> functionList, List<OrderBy> groupByList, boolean isMerge) throws RuntimeException {
        super(sortingCursor, null, sortingCursor.getOrderByList());
        this.executeContext = executeContext;
        this.isMerge = isMerge;
        this.groupByColumnMetaDataList.addAll(Utils.convertOrderByListToColumnMetaDataListWithLogicTables(groupByList));
        for (Function function : functionList) {
            if (function.getFunctionType().equals(FunctionType.aggregate_function)) {
                this.aggregateFunctionList.add((Function) function.copy());
            }
        }
        for (Function function : functionList) {
            if (function.getFunctionType().equals(FunctionType.scalar)) {
                this.scalarFunctionList.add(function);
            }
        }
    }

    private void initCursorMetaData() throws RuntimeException {
        if (cursorMetaDataInited) {
            return;
        }
        cursorMetaDataInited = true;
        //
        rowData = super.next();
        if (rowData == null) {
            return;
        }
        // 把聚合的结果放在最后
        CursorMetaData cursorMetaData = rowData.getParentCursorMetaData();
        List<ColumnMetaData> columnMetaDataList = new ArrayList<ColumnMetaData>(cursorMetaData.getColumnMetaDataList().size() + this.aggregateFunctionList.size());
        columnMetaDataList.addAll(cursorMetaData.getColumnMetaDataList());

        for (Function function : this.aggregateFunctionList) {
            Integer index = cursorMetaData.getIndex(function.getTableName(), function.getColumnName(), function.getAlias());
            if (index == null) {
                addColumnMetaData(columnMetaDataList, function);
            }
        }
        for (Function function : this.scalarFunctionList) {
            Integer index = cursorMetaData.getIndex(function.getTableName(), function.getColumnName(), function.getAlias());
            if (index == null) {
                addColumnMetaData(columnMetaDataList, function);
            }
        }
        this.cursorMetaData = CursorMetaDataImpl.buildCursorMetaData(columnMetaDataList, columnMetaDataList.size());
    }

    void addColumnMetaData(List<ColumnMetaData> columnMetaDataList, Item item) {
        String columnName = item.getColumnName();
        Type type = null;
        // 函数在Map和Reduce过程中的返回类型可以不同
        // 如Avg，map过程返回String,reduce过程中返回数字类型
        if (this.isMerge()) {
            type = item.getType();
        } else {
            if (item instanceof Function) {
                if (((Function) item).getFunctionType().equals(FunctionType.aggregate_function)) {
                    type = ((AggregateFunction) ((Function) item).getExtraFunction()).getMapReturnType();
                } else {
                    type = item.getType();
                }
            } else {
                type = item.getType();
            }
        }
        ColumnMetaData columnMetaData = new ColumnMetaData(Utils.getTableName(item.getTableName()), columnName, item.getAlias(), type, true);
        columnMetaDataList.add(columnMetaData);
    }

    //
    public RowValues next() throws RuntimeException {
        initCursorMetaData();
        if (end) {
            return null;
        }
        if (isFirst) {
            if (rowData == null) {
                end = true;
                return null;
            }
            isFirst = false;
            //
            for (Function aggregateFunction : aggregateFunctionList) {//scalarFunctionList不用进行处理
                aggregateFunction.getExtraFunction().clear();
            }
        }

        // 初始化currentGroupByValue，并把当前第一条记录中的值放进去
        if ($.isNotNullAndHasElement(groupByColumnMetaDataList)) {
            if (this.groupByColumnMetaDataToValueMap == null) {
                this.groupByColumnMetaDataToValueMap = new HashMap<ColumnMetaData, Object>();
            }
            if (rowData == null) {
                groupByColumnMetaDataToValueMap = null;
            } else {
                for (ColumnMetaData columnMetaData : groupByColumnMetaDataList) {
                    Object value = Utils.getObject(rowData, rowData.getParentCursorMetaData(), columnMetaData.getTableName(), columnMetaData.getColumnName(), columnMetaData.getAlias());
                    groupByColumnMetaDataToValueMap.put(columnMetaData, value);
                }
            }

        }

        RowValues arrayRowData = new ArrayRowValues(cursorMetaData.getColumnMetaDataList().size(), cursorMetaData);
        RowValues rowData = this.rowData;
        // 这里无论KV是否是null，第一次都应该让函数来处理
        if (rowData == null && aggregateFunctionList.isEmpty()) {
            end = true;
            return null;
        }

        if (rowData != null) {
            // cursorMeta后面有函数，所以以kv的meta为准
            for (int i = 0; i < rowData.getParentCursorMetaData().getColumnMetaDataList().size(); i++) {
                ColumnMetaData columnMetaData = cursorMetaData.getColumnMetaDataList().get(i);
                Integer index = rowData.getParentCursorMetaData().getIndex(columnMetaData.getTableName(), columnMetaData.getColumnName(), columnMetaData.getAlias());
                arrayRowData.setObject(i, rowData.getObject(index));
            }
        }

        if (!aggregateFunctionList.isEmpty() || !this.groupByColumnMetaDataList.isEmpty()) {
            do {
                // 如果组的值发生了变化，则返回一条记录
                if (isGroupByColumnMetaDataToValueMapChanged(rowData)) {
                    this.rowData = rowData;
                    break;
                }
                for (Function function : aggregateFunctionList) {
                    if (this.isMerge() && !function.isNeedDistinctArg()) {
                        ((AggregateFunction) function.getExtraFunction()).reduce(executeContext, rowData);
                    } else {
                        ((AggregateFunction) function.getExtraFunction()).map(executeContext, rowData);
                    }
                }
            } while ((rowData = super.next()) != null);
        } else {
            rowData = super.next();
            this.rowData = rowData;
        }

        // 将函数的结果放到结果集中
        this.putAggregateFunctionListResultToRowData(arrayRowData, aggregateFunctionList);
        for (Function function : aggregateFunctionList) {
            function.getExtraFunction().clear();
        }
        // 对于aggregate函数，需要遍历所有结果集
        // 当两者同时存在时，scalar函数只处理第一条结果
        // mysql是这样做的
        // 对于scalar函数，只需要取一条结果
        for (Function function : this.scalarFunctionList) {
            Object object = ((ScalarFunction) function.getExtraFunction()).scalarCalucate(executeContext, arrayRowData);
            this.putScalarFunctionResultToRowData(arrayRowData, function, object);
        }
        end = (rowData == null);
        return arrayRowData;
    }

    private boolean isGroupByColumnMetaDataToValueMapChanged(RowValues rowData) {
        if ($.isNotNullAndHasElement(groupByColumnMetaDataList)) {
            if (this.groupByColumnMetaDataToValueMap == null) {
                return false;
            }
            if (rowData == null) {
                return true;
            }
            for (ColumnMetaData columnMetaData : this.groupByColumnMetaDataToValueMap.keySet()) {
                Object object = Utils.getObject(rowData, rowData.getParentCursorMetaData(), columnMetaData.getTableName(), columnMetaData.getColumnName(), columnMetaData.getAlias());
                Object currentGroupByValue = this.groupByColumnMetaDataToValueMap.get(columnMetaData);
                if (object == null) {
                    if (currentGroupByValue != null) {
                        return true;
                    }
                } else {
                    if (currentGroupByValue == null) {
                        return true;
                    }
                    if (!object.equals(currentGroupByValue)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }


    public RowValues first() throws RuntimeException {
        this.end = false;
        this.isFirst = true;
        super.beforeFirst();
        return this.next();

    }

    public void beforeFirst() throws RuntimeException {
        cursorMetaDataInited = false;
        this.end = false;
        this.isFirst = true;
        super.beforeFirst();
    }


    void putAggregateFunctionListResultToRowData(RowValues rowData, List<Function> functionList) {
        for (Function function : functionList) {
            Integer index = this.cursorMetaData.getIndex(function.getTableName(), function.getColumnName(), function.getAlias());
            Object object = ((AggregateFunction) function.getExtraFunction()).getResult();
            if (object instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) object;
                for (Entry<String, Object> entry : map.entrySet()) {
                    rowData.setObject(index, entry.getValue());
                }
            } else {
                rowData.setObject(index, object);
            }
        }
    }

    void putScalarFunctionResultToRowData(RowValues rowData, Function function, Object object) {
        Integer index = this.cursorMetaData.getIndex(function.getTableName(), function.getColumnName(), function.getAlias());
        if (object instanceof Map) {
            Map<String, Object> map = (Map<String, Object>) object;
            for (Entry<String, Object> entry : map.entrySet()) {
                rowData.setObject(index, entry.getValue());
            }
        } else {
            rowData.setObject(index, object);
        }
    }


    public String toString() {
        return toStringWithInden(0);
    }

    public String toStringWithInden(int inden) {
        initCursorMetaData();
        StringBuilder sb = new StringBuilder();
        String tab = GeneralUtil.getTab(inden);
        sb.append(tab).append("【aggregate_function cursor .  aggregateFunctionList").append(aggregateFunctionList).append("\n");
        Utils.printCursorMetaData(cursorMetaData, inden, sb);
        Utils.printOrderByList(orderByList, inden, sb);
        sb.append(super.toStringWithInden(inden));
        return sb.toString();
    }
}
